package lib.android.timingbar.com.view.handSign;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import lib.android.timingbar.com.view.R;
import lib.android.timingbar.com.view.handSign.point.DrawPoint;
import lib.android.timingbar.com.view.handSign.point.PointUtil;
import lib.android.timingbar.com.view.handSign.point.TimedPoint;
import lib.android.timingbar.com.view.handSign.util.SignFileOutputStream;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

/**
 * HandSignView
 * -----------------------------------------------------------------------------------------------------------------------------------
 * 实现手写签名的自定义view
 *
 * @author rqmei on 2018/9/5
 */

public class HandSignView extends View {
    List<TimedPoint> points = new ArrayList<> ();
    Stack<TimedPoint> cachePoints = new Stack<> ();
    PointUtil pointUtil = new PointUtil ();
    private Bitmap mBitmap;
    private Canvas mCanvas;
    private Paint mPaint;
    private boolean isSign = false, isStartWrite = false;
    HandWriteInterface handEvent;

    private int mBackColor = Color.TRANSPARENT;

    public HandSignView(Context context) {
        this (context, null);
    }

    public HandSignView(Context context, AttributeSet attrs) {
        this (context, attrs, 0);
    }

    public HandSignView(Context context, AttributeSet attrs, int defStyleAttr) {
        super (context, attrs, defStyleAttr);
        init (context, attrs);
    }

    @SuppressLint({"Recycle", "CustomViewStyleable"})
    private void init(Context context, AttributeSet attrs) {
        TypedArray a = context.obtainStyledAttributes (attrs, R.styleable.HandWriteView);
        int maxWidth = a.getDimensionPixelSize (R.styleable.HandWriteView_paintMaxWidth, 16);
        int minWidth = a.getDimensionPixelSize (R.styleable.HandWriteView_paintMinWidth, 8);
        int paintColor = a.getColor (R.styleable.HandWriteView_paintColor, Color.BLACK);
        pointUtil.setWidth (minWidth, maxWidth);
        mPaint = new Paint ();
        mPaint.setColor (paintColor);
        mPaint.setStrokeWidth (10);
        mPaint.setAntiAlias (true);
        mPaint.setStyle (Paint.Style.STROKE);
        mPaint.setStrokeCap (Paint.Cap.ROUND);
        mPaint.setStrokeJoin (Paint.Join.ROUND);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX ();
        float y = event.getY ();
        switch (event.getAction ()) {
            case MotionEvent.ACTION_DOWN:
                if (handEvent != null && !isStartWrite) {
                    isStartWrite = true;
                    handEvent.onStartWrite (isStartWrite);
                }
                getParent ().requestDisallowInterceptTouchEvent (true);
                points.clear ();
                addPoint (getNewPoint (x, y));
                break;
            case MotionEvent.ACTION_MOVE:
                addPoint (getNewPoint (x, y));
                break;
            case MotionEvent.ACTION_UP:
                isSign = true;
                addPoint (getNewPoint (x, y));
                getParent ().requestDisallowInterceptTouchEvent (false);
                break;
        }
        invalidate ();
        return true;
    }

    private TimedPoint getNewPoint(float x, float y) {
        if (cachePoints.empty ()) {
            return new TimedPoint (x, y);
        } else
            return cachePoints.pop ().set (x, y);
    }

    private void recyclePoint(TimedPoint point) {
        cachePoints.push (point);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //画背景 如果有需要的话
        super.onDraw (canvas);
        if (mBitmap != null) {
            canvas.drawBitmap (mBitmap, 0, 0, null);
        }
    }

    private void addPoint(TimedPoint point) {
        points.add (point);
        if (points.size () > 3) {
            ensureSignatureBitmap ();
            TimedPoint s0 = points.get (0);
            TimedPoint s1 = points.get (1);
            TimedPoint s2 = points.get (2);
            TimedPoint s3 = points.get (3);
            float cx1 = s1.x + (s2.x - s0.x) / 4;
            float cy1 = s1.y + (s2.y - s0.y) / 4;
            float cx2 = s2.x - (s3.x - s1.x) / 4;
            float cy2 = s2.y - (s3.y - s1.y) / 4;
            pointUtil.set (s1, getNewPoint (cx1, cy1), getNewPoint (cx2, cy2), s2);
            float originalWidth = mPaint.getStrokeWidth ();
            float drawSteps = (float) Math.floor (pointUtil.length ());
            for (int i = 0; i < drawSteps; i++) {
                float t = (float) i / drawSteps;
                DrawPoint drawPoint = pointUtil.calculate (t);
                mPaint.setStrokeWidth (drawPoint.width);
                mCanvas.drawPoint (drawPoint.x, drawPoint.y, mPaint);
            }
            mPaint.setStrokeWidth (originalWidth);
            recyclePoint (points.remove (0));
            recyclePoint (pointUtil.control1);
            recyclePoint (pointUtil.control2);
        } else if (points.size () == 1) {
            points.add (getNewPoint (point.x, point.y));
        }
    }

    private void ensureSignatureBitmap() {
        if (mBitmap == null) {
            mBitmap = Bitmap.createBitmap (getWidth (), getHeight (), Bitmap.Config.ARGB_8888);
            mCanvas = new Canvas (mBitmap);
        }
    }


    /**
     * 保存画板
     *
     * @param path 保存到路劲
     */

    public void save(String path) throws IOException {
        save (path, false, 0, false);
    }

    public void save(String path, boolean isEncrypt) throws IOException {
        save (path, false, 0, isEncrypt);
    }

    /**
     * 保存画板
     *
     * @param path       保存到路径
     * @param clearBlank 是否清楚空白区域
     * @param blank      边缘空白区域
     * @param isEncrypt  加密存储，选择加密存储会自动追加后缀为.sign
     */
    public void save(String path, boolean clearBlank, int blank, boolean isEncrypt) throws IOException {
        //主要是先创建一张白色图片，然后把原来的绘制至上去
        if (mBitmap != null) {
            Bitmap bitmap = mBitmap.copy (Bitmap.Config.ARGB_8888, true);
            Canvas canvas = new Canvas (bitmap);
            canvas.drawColor (Color.WHITE);
            canvas.drawBitmap (mBitmap, 0, 0, null);
            if (clearBlank) {
                bitmap = clearBlank (bitmap, blank);
            }
            if (isEncrypt)
                path = path + ".sign";
            ByteArrayOutputStream bos = new ByteArrayOutputStream ();
            bitmap.compress (Bitmap.CompressFormat.PNG, 100, bos);
            byte[] buffer = bos.toByteArray ();
            if (buffer != null) {
                File file = new File (path);
                if (file.exists ()) {
                    file.delete ();
                }
                OutputStream outputStream = isEncrypt ? new SignFileOutputStream (file) : new FileOutputStream (file);
                outputStream.write (buffer);
                outputStream.close ();
            }
        }
    }

    /**
     * 逐行扫描 清除边界空白。
     *
     * @param bp
     * @param blank 边距留多少个像素
     * @return
     */
    private Bitmap clearBlank(Bitmap bp, int blank) {
        int HEIGHT = bp.getHeight ();
        int WIDTH = bp.getWidth ();
        int top = 0, left = 0, right = WIDTH, bottom = HEIGHT;
        int[] pixs = new int[WIDTH];
        boolean isStop;
        for (int y = 0; y < HEIGHT; y++) {
            bp.getPixels (pixs, 0, WIDTH, 0, y, WIDTH, 1);
            isStop = false;
            for (int pix : pixs) {
                if (pix != mBackColor) {
                    top = y;
                    isStop = true;
                    break;
                }
            }
            if (isStop) {
                break;
            }
        }
        for (int y = HEIGHT - 1; y >= 0; y--) {
            bp.getPixels (pixs, 0, WIDTH, 0, y, WIDTH, 1);
            isStop = false;
            for (int pix : pixs) {
                if (pix != mBackColor) {
                    bottom = y;
                    isStop = true;
                    break;
                }
            }
            if (isStop) {
                break;
            }
        }

        int scanHeight = bottom - top;
        pixs = new int[scanHeight];
        for (int x = 0; x < WIDTH; x++) {
            bp.getPixels (pixs, 0, 1, x, top, 1, scanHeight);
            isStop = false;
            for (int pix : pixs) {
                if (pix != mBackColor) {
                    left = x;
                    isStop = true;
                    break;
                }
            }
            if (isStop) {
                break;
            }
        }
        for (int x = WIDTH - 1; x > 0; x--) {
            bp.getPixels (pixs, 0, 1, x, top, 1, scanHeight);
            isStop = false;
            for (int pix : pixs) {
                if (pix != mBackColor) {
                    right = x;
                    isStop = true;
                    break;
                }
            }
            if (isStop) {
                break;
            }
        }
        if (blank < 0) {
            blank = 0;
        }
        left = left - blank > 0 ? left - blank : 0;
        top = top - blank > 0 ? top - blank : 0;
        right = right + blank > WIDTH - 1 ? WIDTH - 1 : right + blank;
        bottom = bottom + blank > HEIGHT - 1 ? HEIGHT - 1 : bottom + blank;
        return Bitmap.createBitmap (bp, left, top, right - left, bottom - top);
    }

    public void setPaintColor(int paintColor) {
        mPaint.setColor (paintColor);
    }

    public void setPaintWidth(int mMinWidth, int mMaxWidth) {
        if (mMinWidth > 0 && mMaxWidth > 0 && mMinWidth <= mMaxWidth)
            pointUtil.setWidth (mMinWidth, mMaxWidth);
    }

    public void clear() {
        if (mBitmap != null && !mBitmap.isRecycled ()) {
            mBitmap.recycle ();
        }
        mBitmap = null;
        ensureSignatureBitmap ();
        invalidate ();
        isSign = false;
        if (handEvent != null && isStartWrite) {
            isStartWrite = false;
            handEvent.onStartWrite (isStartWrite);
        }
    }

    public boolean isSign() {
        return isSign;
    }

    public void setHandWriteInterface(HandWriteInterface handEvent) {
        this.handEvent = handEvent;
    }

    public interface HandWriteInterface {
        void onStartWrite(boolean isStartWrite);
    }
}
